﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics;

namespace MyClever.Lib
{
    /// <summary>
    /// The PluginManager is a singleton class, which is responsible for the following main tasks:
    /// * loads an explict plugin builder by pluginPath and pluginClassName
    /// * creates a list of available plugins, which we can obstruct in the pluginEditor
    /// </summary>
    public sealed class PluginManager
    {
        static readonly PluginManager instance = new PluginManager();
        private List<AvailablePlugin> loadedPlugins = new List<AvailablePlugin>();
        
        private AppDomain appDomain;
        RemotePluginReflector remotePluginReflector;

        public string PluginRootDirectory
        {
            get
            {
                return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MyCleverFriend\\Plugins");
            }
        }

        public static PluginManager Instance
        {
            get
            {
                return instance;
            }
        }

        public List<AvailablePlugin> Plugins
        {
            get
            {
                return this.loadedPlugins;
            }
        }

        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static PluginManager()
        {
        }
        
        /// <summary>
        /// Constructs a plugin manager
        /// </summary>
        /// <param name="pluginRelativePath">The relative path to the plugins directory</param>
        public PluginManager()
        {
            string privateBinPath = String.Empty;
            foreach (PluginPath pluginOn in this.GetPluginDirectories())
            {
                privateBinPath += pluginOn.RelativeDataPath + ";";
            }

            appDomain = AppDomainLoader.CreateAppDomain("PluginManager", privateBinPath);
            remotePluginReflector = (RemotePluginReflector)AppDomainLoader.CreateInstanceInAppDomain(appDomain, "MyCleverLib", "MyClever.Lib.RemotePluginReflector");
            Debug.Assert(remotePluginReflector != null);
        }

        /// <summary>
        /// Loads an explict plugin builder by pluginPath and pluginClassName
        /// </summary>
        public PluginBuilder GetPluginBuilder(PluginPath pluginPath, string pluginClass, ProgrammingLanguage pluginLanguage)
        {
            AppDomain pluginAppDomain = AppDomainLoader.CreateAppDomain(pluginPath, pluginClass);
            RemotePluginLoader remotePluginLoader = (RemotePluginLoader)AppDomainLoader.CreateInstanceInAppDomain(pluginAppDomain, "MyCleverLib", "MyClever.Lib.RemotePluginLoader");
            Debug.Assert(remotePluginLoader != null);

            remotePluginLoader.Load(pluginPath, pluginClass, pluginLanguage);
            return remotePluginLoader.PluginBuilder;
        }

        /// <summary>
        /// Loads the plugin from the given plugin file.
        /// </summary>
        public void LoadPluginsFromDirectory()
        {
            foreach (PluginPath pluginPathOn in this.GetPluginDirectories())
            {
                // is this a c# plugin?
                if (pluginPathOn.FileExtension.Equals(".dll"))
                {
                    List<KeyValuePair<string, string>> pluginNames = remotePluginReflector.ExtractPlugins(pluginPathOn, ProgrammingLanguage.CSharp);
                    foreach (KeyValuePair<string, string> pluginNameOn in pluginNames)
                    {
                        this.loadedPlugins.Add(new AvailablePlugin(pluginPathOn, pluginNameOn.Key, pluginNameOn.Value, ProgrammingLanguage.CSharp));
                    }
                }
                // is this plugin written in python
                if (pluginPathOn.FileExtension.Equals(".py"))
                {
                    this.loadedPlugins.Add(new AvailablePlugin(pluginPathOn, Path.GetFileNameWithoutExtension(pluginPathOn.FullPath), ProgrammingLanguage.Python));
                }
            }
        }

        private List<PluginPath> GetPluginDirectories()
        {
            List<PluginPath> pluginDirectories = new List<PluginPath>();

            foreach (string fileOn in Directory.GetFiles(this.PluginRootDirectory, "*.*", SearchOption.AllDirectories))
            {
                if (true == IsValidPluginBasePath(fileOn))
                {
                    pluginDirectories.Add(new PluginPath(fileOn));
                }
            }
            return pluginDirectories;
        }

        /// <summary>
        /// Follows the given plugin path the expected data structure? (C:\...\CompanyName\PluginName\PluginVersion\)
        /// </summary>
        /// <returns>Returns true, if the given plugin is valid.</returns>
        private static bool IsValidPluginBasePath(string pluginBasePath)
        {
            Match match = Regex.Match(pluginBasePath, @"[A-Za-z0-9-_ ]+\\[A-Za-z0-9-_ ]+\\[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\\data", RegexOptions.IgnoreCase);
            return match.Success;
        }
    }
}
